/**********
This library is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version. (See <http://www.gnu.org/copyleft/lesser.html>.)

This library is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
more details.

You should have received a copy of the GNU Lesser General Public License
along with this library; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
**********/
// "liveMedia"
// Copyright (c) 1996-2018 Live Networks, Inc.  All rights reserved.
// A 'ServerMediaSubsession' object that creates new, unicast, "RTPSink"s
// on demand.
// Implementation

#include "include/OnDemandServerMediaSubsession.hh"
#include "../groupsock/include/GroupsockHelper.hh"

OnDemandServerMediaSubsession
::OnDemandServerMediaSubsession(UsageEnvironment &env,
                                Boolean reuseFirstSource,
                                portNumBits initialPortNum,
                                Boolean multiplexRTCPWithRTP)
        : ServerMediaSubsession(env),
          fSDPLines(NULL), fReuseFirstSource(reuseFirstSource),
          fMultiplexRTCPWithRTP(multiplexRTCPWithRTP), fLastStreamToken(NULL),
          fAppHandlerTask(NULL), fAppHandlerClientData(NULL) {
    fDestinationsHashTable = HashTable::create(ONE_WORD_HASH_KEYS);
    if (fMultiplexRTCPWithRTP) {
        fInitialPortNum = initialPortNum;
    } else {
        // Make sure RTP ports are even-numbered:
        fInitialPortNum = (initialPortNum + 1) & ~1;
    }
    gethostname(fCNAME, sizeof fCNAME);
    fCNAME[sizeof fCNAME - 1] = '\0'; // just in case
}

OnDemandServerMediaSubsession::~OnDemandServerMediaSubsession() {
    delete[] fSDPLines;

    // Clean out the destinations hash table:
    while (1) {
        Destinations *destinations
                = (Destinations *) (fDestinationsHashTable->RemoveNext());
        if (destinations == NULL) break;
        delete destinations;
    }
    delete fDestinationsHashTable;
}

char const *
OnDemandServerMediaSubsession::sdpLines() {
    if (fSDPLines == NULL) {
        // We need to construct a set of SDP lines that describe this
        // subsession (as a unicast stream).  To do so, we first create
        // dummy (unused) source and "RTPSink" objects,
        // whose parameters we use for the SDP lines:
        unsigned estBitrate;
        FramedSource *inputSource = createNewStreamSource(0, estBitrate);
        if (inputSource == NULL) return NULL; // file not found

        struct in_addr dummyAddr;
        dummyAddr.s_addr = 0;
        Groupsock *dummyGroupsock = createGroupsock(dummyAddr, 0);
        unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamic
        RTPSink *dummyRTPSink = createNewRTPSink(dummyGroupsock, rtpPayloadType, inputSource);
        if (dummyRTPSink != NULL && dummyRTPSink->estimatedBitrate() > 0)
            estBitrate = dummyRTPSink->estimatedBitrate();

        setSDPLinesFromRTPSink(dummyRTPSink, inputSource, estBitrate);
        Medium::close(dummyRTPSink);
        delete dummyGroupsock;
        closeStreamSource(inputSource);
    }

    return fSDPLines;
}

void OnDemandServerMediaSubsession
::getStreamParameters(unsigned clientSessionId,
                      netAddressBits clientAddress,
                      Port const &clientRTPPort,
                      Port const &clientRTCPPort,
                      int tcpSocketNum,
                      unsigned char rtpChannelId,
                      unsigned char rtcpChannelId,
                      netAddressBits &destinationAddress,
                      u_int8_t & /*destinationTTL*/,
                      Boolean &isMulticast,
                      Port &serverRTPPort,
                      Port &serverRTCPPort,
                      void *&streamToken) {
    if (destinationAddress == 0) destinationAddress = clientAddress;
    struct in_addr destinationAddr;
    destinationAddr.s_addr = destinationAddress;
    isMulticast = False;

    if (fLastStreamToken != NULL && fReuseFirstSource) {
        // Special case: Rather than creating a new 'StreamState',
        // we reuse the one that we've already created:
        serverRTPPort = ((StreamState *) fLastStreamToken)->serverRTPPort();
        serverRTCPPort = ((StreamState *) fLastStreamToken)->serverRTCPPort();
        ++((StreamState *) fLastStreamToken)->referenceCount();
        streamToken = fLastStreamToken;
    } else {
        // Normal case: Create a new media source:
        unsigned streamBitrate;
        FramedSource *mediaSource
                = createNewStreamSource(clientSessionId, streamBitrate);

        // Create 'groupsock' and 'sink' objects for the destination,
        // using previously unused server port numbers:
        RTPSink *rtpSink = NULL;
        BasicUDPSink *udpSink = NULL;
        Groupsock *rtpGroupsock = NULL;
        Groupsock *rtcpGroupsock = NULL;

        if (clientRTPPort.num() != 0 || tcpSocketNum >= 0) { // Normal case: Create destinations
            portNumBits serverPortNum;
            if (clientRTCPPort.num() == 0) {
                // We're streaming raw UDP (not RTP). Create a single groupsock:
                NoReuse dummy(envir()); // ensures that we skip over ports that are already in use
                for (serverPortNum = fInitialPortNum;; ++serverPortNum) {
                    struct in_addr dummyAddr;
                    dummyAddr.s_addr = 0;

                    serverRTPPort = serverPortNum;
                    rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort);
                    if (rtpGroupsock->socketNum() >= 0) break; // success
                }

                udpSink = BasicUDPSink::createNew(envir(), rtpGroupsock);
            } else {
                // Normal case: We're streaming RTP (over UDP or TCP).  Create a pair of
                // groupsocks (RTP and RTCP), with adjacent port numbers (RTP port number even).
                // (If we're multiplexing RTCP and RTP over the same port number, it can be odd or even.)
                NoReuse dummy(envir()); // ensures that we skip over ports that are already in use
                for (portNumBits serverPortNum = fInitialPortNum;; ++serverPortNum) {
                    struct in_addr dummyAddr;
                    dummyAddr.s_addr = 0;

                    serverRTPPort = serverPortNum;
                    rtpGroupsock = createGroupsock(dummyAddr, serverRTPPort);
                    if (rtpGroupsock->socketNum() < 0) {
                        delete rtpGroupsock;
                        continue; // try again
                    }

                    if (fMultiplexRTCPWithRTP) {
                        // Use the RTP 'groupsock' object for RTCP as well:
                        serverRTCPPort = serverRTPPort;
                        rtcpGroupsock = rtpGroupsock;
                    } else {
                        // Create a separate 'groupsock' object (with the next (odd) port number) for RTCP:
                        serverRTCPPort = ++serverPortNum;
                        rtcpGroupsock = createGroupsock(dummyAddr, serverRTCPPort);
                        if (rtcpGroupsock->socketNum() < 0) {
                            delete rtpGroupsock;
                            delete rtcpGroupsock;
                            continue; // try again
                        }
                    }

                    break; // success
                }

                unsigned char rtpPayloadType = 96 + trackNumber() - 1; // if dynamic
                rtpSink = createNewRTPSink(rtpGroupsock, rtpPayloadType, mediaSource);
                if (rtpSink != NULL && rtpSink->estimatedBitrate() > 0)
                    streamBitrate = rtpSink->estimatedBitrate();
            }

            // Turn off the destinations for each groupsock.  They'll get set later
            // (unless TCP is used instead):
            if (rtpGroupsock != NULL) rtpGroupsock->removeAllDestinations();
            if (rtcpGroupsock != NULL) rtcpGroupsock->removeAllDestinations();

            if (rtpGroupsock != NULL) {
                // Try to use a big send buffer for RTP -  at least 0.1 second of
                // specified bandwidth and at least 50 KB
                unsigned rtpBufSize = streamBitrate * 25 / 2; // 1 kbps * 0.1 s = 12.5 bytes
                if (rtpBufSize < 50 * 1024) rtpBufSize = 50 * 1024;
                increaseSendBufferTo(envir(), rtpGroupsock->socketNum(), rtpBufSize);
            }
        }

        // Set up the state of the stream.  The stream will get started later:
        streamToken = fLastStreamToken
                = new StreamState(*this, serverRTPPort, serverRTCPPort, rtpSink, udpSink,
                                  streamBitrate, mediaSource,
                                  rtpGroupsock, rtcpGroupsock);
    }

    // Record these destinations as being for this client session id:
    Destinations *destinations;
    if (tcpSocketNum < 0) { // UDP
        destinations = new Destinations(destinationAddr, clientRTPPort, clientRTCPPort);
    } else { // TCP
        destinations = new Destinations(tcpSocketNum, rtpChannelId, rtcpChannelId);
    }
    fDestinationsHashTable->Add((char const *) clientSessionId, destinations);
}

void OnDemandServerMediaSubsession::startStream(unsigned clientSessionId,
                                                void *streamToken,
                                                TaskFunc *rtcpRRHandler,
                                                void *rtcpRRHandlerClientData,
                                                unsigned short &rtpSeqNum,
                                                unsigned &rtpTimestamp,
                                                ServerRequestAlternativeByteHandler *serverRequestAlternativeByteHandler,
                                                void *serverRequestAlternativeByteHandlerClientData) {
    StreamState *streamState = (StreamState *) streamToken;
    Destinations *destinations
            = (Destinations *) (fDestinationsHashTable->Lookup((char const *) clientSessionId));
    if (streamState != NULL) {
        streamState->startPlaying(destinations, clientSessionId,
                                  rtcpRRHandler, rtcpRRHandlerClientData,
                                  serverRequestAlternativeByteHandler,
                                  serverRequestAlternativeByteHandlerClientData);
        RTPSink *rtpSink = streamState->rtpSink(); // alias
        if (rtpSink != NULL) {
            rtpSeqNum = rtpSink->currentSeqNo();
            rtpTimestamp = rtpSink->presetNextTimestamp();
        }
    }
}

void OnDemandServerMediaSubsession::pauseStream(unsigned /*clientSessionId*/,
                                                void *streamToken) {
    // Pausing isn't allowed if multiple clients are receiving data from
    // the same source:
    if (fReuseFirstSource) return;

    StreamState *streamState = (StreamState *) streamToken;
    if (streamState != NULL) streamState->pause();
}

void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
                                               void *streamToken, double &seekNPT,
                                               double streamDuration, u_int64_t &numBytes) {
    numBytes = 0; // by default: unknown

    // Seeking isn't allowed if multiple clients are receiving data from the same source:
    if (fReuseFirstSource) return;

    StreamState *streamState = (StreamState *) streamToken;
    if (streamState != NULL && streamState->mediaSource() != NULL) {
        seekStreamSource(streamState->mediaSource(), seekNPT, streamDuration, numBytes);

        streamState->startNPT() = (float) seekNPT;
        RTPSink *rtpSink = streamState->rtpSink(); // alias
        if (rtpSink != NULL) rtpSink->resetPresentationTimes();
    }
}

void OnDemandServerMediaSubsession::seekStream(unsigned /*clientSessionId*/,
                                               void *streamToken, char *&absStart, char *&absEnd) {
    // Seeking isn't allowed if multiple clients are receiving data from the same source:
    if (fReuseFirstSource) return;

    StreamState *streamState = (StreamState *) streamToken;
    if (streamState != NULL && streamState->mediaSource() != NULL) {
        seekStreamSource(streamState->mediaSource(), absStart, absEnd);
    }
}

void OnDemandServerMediaSubsession::nullSeekStream(unsigned /*clientSessionId*/, void *streamToken,
                                                   double streamEndTime, u_int64_t &numBytes) {
    numBytes = 0; // by default: unknown

    StreamState *streamState = (StreamState *) streamToken;
    if (streamState != NULL && streamState->mediaSource() != NULL) {
        // Because we're not seeking here, get the current NPT, and remember it as the new 'start' NPT:
        streamState->startNPT() = getCurrentNPT(streamToken);

        double duration = streamEndTime - streamState->startNPT();
        if (duration < 0.0) duration = 0.0;
        setStreamSourceDuration(streamState->mediaSource(), duration, numBytes);

        RTPSink *rtpSink = streamState->rtpSink(); // alias
        if (rtpSink != NULL) rtpSink->resetPresentationTimes();
    }
}

void OnDemandServerMediaSubsession::setStreamScale(unsigned /*clientSessionId*/,
                                                   void *streamToken, float scale) {
    // Changing the scale factor isn't allowed if multiple clients are receiving data
    // from the same source:
    if (fReuseFirstSource) return;

    StreamState *streamState = (StreamState *) streamToken;
    if (streamState != NULL && streamState->mediaSource() != NULL) {
        setStreamSourceScale(streamState->mediaSource(), scale);
    }
}

float OnDemandServerMediaSubsession::getCurrentNPT(void *streamToken) {
    do {
        if (streamToken == NULL) break;

        StreamState *streamState = (StreamState *) streamToken;
        RTPSink *rtpSink = streamState->rtpSink();
        if (rtpSink == NULL) break;

        return streamState->startNPT()
               + (rtpSink->mostRecentPresentationTime().tv_sec -
                  rtpSink->initialPresentationTime().tv_sec)
               + (rtpSink->mostRecentPresentationTime().tv_usec -
                  rtpSink->initialPresentationTime().tv_usec) / 1000000.0f;
    } while (0);

    return 0.0;
}

FramedSource *OnDemandServerMediaSubsession::getStreamSource(void *streamToken) {
    if (streamToken == NULL) return NULL;

    StreamState *streamState = (StreamState *) streamToken;
    return streamState->mediaSource();
}

void OnDemandServerMediaSubsession
::getRTPSinkandRTCP(void *streamToken,
                    RTPSink const *&rtpSink, RTCPInstance const *&rtcp) {
    if (streamToken == NULL) {
        rtpSink = NULL;
        rtcp = NULL;
        return;
    }

    StreamState *streamState = (StreamState *) streamToken;
    rtpSink = streamState->rtpSink();
    rtcp = streamState->rtcpInstance();
}

void OnDemandServerMediaSubsession::deleteStream(unsigned clientSessionId,
                                                 void *&streamToken) {
    StreamState *streamState = (StreamState *) streamToken;

    // Look up (and remove) the destinations for this client session:
    Destinations *destinations
            = (Destinations *) (fDestinationsHashTable->Lookup((char const *) clientSessionId));
    if (destinations != NULL) {
        fDestinationsHashTable->Remove((char const *) clientSessionId);

        // Stop streaming to these destinations:
        if (streamState != NULL) streamState->endPlaying(destinations, clientSessionId);
    }

    // Delete the "StreamState" structure if it's no longer being used:
    if (streamState != NULL) {
        if (streamState->referenceCount() > 0) --streamState->referenceCount();
        if (streamState->referenceCount() == 0) {
            delete streamState;
            streamToken = NULL;
        }
    }

    // Finally, delete the destinations themselves:
    delete destinations;
}

char const *OnDemandServerMediaSubsession
::getAuxSDPLine(RTPSink *rtpSink, FramedSource * /*inputSource*/) {
    // Default implementation:
    return rtpSink == NULL ? NULL : rtpSink->auxSDPLine();
}

void OnDemandServerMediaSubsession::seekStreamSource(FramedSource * /*inputSource*/,
                                                     double & /*seekNPT*/,
                                                     double /*streamDuration*/,
                                                     u_int64_t &numBytes) {
    // Default implementation: Do nothing
    numBytes = 0;
}

void OnDemandServerMediaSubsession::seekStreamSource(FramedSource * /*inputSource*/,
                                                     char *&absStart, char *&absEnd) {
    // Default implementation: do nothing (but delete[] and assign "absStart" and "absEnd" to NULL, to show that we don't handle this)
    delete[] absStart;
    absStart = NULL;
    delete[] absEnd;
    absEnd = NULL;
}

void OnDemandServerMediaSubsession
::setStreamSourceScale(FramedSource * /*inputSource*/, float /*scale*/) {
    // Default implementation: Do nothing
}

void OnDemandServerMediaSubsession
::setStreamSourceDuration(FramedSource * /*inputSource*/, double /*streamDuration*/,
                          u_int64_t &numBytes) {
    // Default implementation: Do nothing
    numBytes = 0;
}

void OnDemandServerMediaSubsession::closeStreamSource(FramedSource *inputSource) {
    Medium::close(inputSource);
}

Groupsock *OnDemandServerMediaSubsession
::createGroupsock(struct in_addr const &addr, Port port) {
    // Default implementation; may be redefined by subclasses:
    return new Groupsock(envir(), addr, port, 255);
}

RTCPInstance *OnDemandServerMediaSubsession
::createRTCP(Groupsock *RTCPgs, unsigned totSessionBW, /* in kbps */
             unsigned char const *cname, RTPSink *sink) {
    // Default implementation; may be redefined by subclasses:
    return RTCPInstance::createNew(envir(), RTCPgs, totSessionBW, cname, sink,
                                   NULL/*we're a server*/);
}

void OnDemandServerMediaSubsession
::setRTCPAppPacketHandler(RTCPAppHandlerFunc *handler, void *clientData) {
    fAppHandlerTask = handler;
    fAppHandlerClientData = clientData;
}

void OnDemandServerMediaSubsession
::sendRTCPAppPacket(u_int8_t subtype, char const *name,
                    u_int8_t *appDependentData, unsigned appDependentDataSize) {
    StreamState *streamState = (StreamState *) fLastStreamToken;
    if (streamState != NULL) {
        streamState->sendRTCPAppPacket(subtype, name, appDependentData, appDependentDataSize);
    }
}

void OnDemandServerMediaSubsession
::setSDPLinesFromRTPSink(RTPSink *rtpSink, FramedSource *inputSource, unsigned estBitrate) {
    if (rtpSink == NULL) return;

    char const *mediaType = rtpSink->sdpMediaType();
    unsigned char rtpPayloadType = rtpSink->rtpPayloadType();
    AddressString ipAddressStr(fServerAddressForSDP);
    char *rtpmapLine = rtpSink->rtpmapLine();
    char const *rtcpmuxLine = fMultiplexRTCPWithRTP ? "a=rtcp-mux\r\n" : "";
    char const *rangeLine = rangeSDPLine();
    char const *auxSDPLine = getAuxSDPLine(rtpSink, inputSource);
    if (auxSDPLine == NULL) auxSDPLine = "";

    char const *const sdpFmt =
            "m=%s %u RTP/AVP %d\r\n"
            "c=IN IP4 %s\r\n"
            "b=AS:%u\r\n"
            "%s"
            "%s"
            "%s"
            "%s"
            "a=control:%s\r\n";
    unsigned sdpFmtSize = strlen(sdpFmt)
                          + strlen(mediaType) + 5 /* max short len */ + 3 /* max char len */
                          + strlen(ipAddressStr.val())
                          + 20 /* max int len */
                          + strlen(rtpmapLine)
                          + strlen(rtcpmuxLine)
                          + strlen(rangeLine)
                          + strlen(auxSDPLine)
                          + strlen(trackId());
    char *sdpLines = new char[sdpFmtSize];
    sprintf(sdpLines, sdpFmt,
            mediaType, // m= <media>
            fPortNumForSDP, // m= <port>
            rtpPayloadType, // m= <fmt list>
            ipAddressStr.val(), // c= address
            estBitrate, // b=AS:<bandwidth>
            rtpmapLine, // a=rtpmap:... (if present)
            rtcpmuxLine, // a=rtcp-mux:... (if present)
            rangeLine, // a=range:... (if present)
            auxSDPLine, // optional extra SDP line
            trackId()); // a=control:<track-id>
    delete[] (char *) rangeLine;
    delete[] rtpmapLine;

    delete[] fSDPLines;
    fSDPLines = strDup(sdpLines);
    delete[] sdpLines;
}


////////// StreamState implementation //////////

static void afterPlayingStreamState(void *clientData) {
    StreamState *streamState = (StreamState *) clientData;
    if (streamState->streamDuration() == 0.0) {
        // When the input stream ends, tear it down.  This will cause a RTCP "BYE"
        // to be sent to each client, teling it that the stream has ended.
        // (Because the stream didn't have a known duration, there was no other
        //  way for clients to know when the stream ended.)
        streamState->reclaim();
    }
    // Otherwise, keep the stream alive, in case a client wants to
    // subsequently re-play the stream starting from somewhere other than the end.
    // (This can be done only on streams that have a known duration.)
}

StreamState::StreamState(OnDemandServerMediaSubsession &master,
                         Port const &serverRTPPort, Port const &serverRTCPPort,
                         RTPSink *rtpSink, BasicUDPSink *udpSink,
                         unsigned totalBW, FramedSource *mediaSource,
                         Groupsock *rtpGS, Groupsock *rtcpGS)
        : fMaster(master), fAreCurrentlyPlaying(False), fReferenceCount(1),
          fServerRTPPort(serverRTPPort), fServerRTCPPort(serverRTCPPort),
          fRTPSink(rtpSink), fUDPSink(udpSink), fStreamDuration(master.duration()),
          fTotalBW(totalBW), fRTCPInstance(NULL) /* created later */,
          fMediaSource(mediaSource), fStartNPT(0.0), fRTPgs(rtpGS), fRTCPgs(rtcpGS) {
}

StreamState::~StreamState() {
    reclaim();
}

void StreamState
::startPlaying(Destinations *dests, unsigned clientSessionId,
               TaskFunc *rtcpRRHandler, void *rtcpRRHandlerClientData,
               ServerRequestAlternativeByteHandler *serverRequestAlternativeByteHandler,
               void *serverRequestAlternativeByteHandlerClientData) {
    if (dests == NULL) return;

    if (fRTCPInstance == NULL && fRTPSink != NULL) {
        // Create (and start) a 'RTCP instance' for this RTP sink:
        fRTCPInstance = fMaster.createRTCP(fRTCPgs, fTotalBW, (unsigned char *) fMaster.fCNAME,
                                           fRTPSink);
        // Note: This starts RTCP running automatically
        fRTCPInstance->setAppHandler(fMaster.fAppHandlerTask, fMaster.fAppHandlerClientData);
    }

    if (dests->isTCP) {
        // Change RTP and RTCP to use the TCP socket instead of UDP:
        if (fRTPSink != NULL) {
            fRTPSink->addStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
            RTPInterface
            ::setServerRequestAlternativeByteHandler(fRTPSink->envir(), dests->tcpSocketNum,
                                                     serverRequestAlternativeByteHandler,
                                                     serverRequestAlternativeByteHandlerClientData);
            // So that we continue to handle RTSP commands from the client
        }
        if (fRTCPInstance != NULL) {
            fRTCPInstance->addStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
            fRTCPInstance->setSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId,
                                                rtcpRRHandler, rtcpRRHandlerClientData);
        }
    } else {
        // Tell the RTP and RTCP 'groupsocks' about this destination
        // (in case they don't already have it):
        if (fRTPgs != NULL) fRTPgs->addDestination(dests->addr, dests->rtpPort, clientSessionId);
        if (fRTCPgs != NULL &&
            !(fRTCPgs == fRTPgs && dests->rtcpPort.num() == dests->rtpPort.num())) {
            fRTCPgs->addDestination(dests->addr, dests->rtcpPort, clientSessionId);
        }
        if (fRTCPInstance != NULL) {
            fRTCPInstance->setSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort,
                                                rtcpRRHandler, rtcpRRHandlerClientData);
        }
    }

    if (fRTCPInstance != NULL) {
        // Hack: Send an initial RTCP "SR" packet, before the initial RTP packet, so that receivers will (likely) be able to
        // get RTCP-synchronized presentation times immediately:
        fRTCPInstance->sendReport();
    }

    if (!fAreCurrentlyPlaying && fMediaSource != NULL) {
        if (fRTPSink != NULL) {
            fRTPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
            fAreCurrentlyPlaying = True;
        } else if (fUDPSink != NULL) {
            fUDPSink->startPlaying(*fMediaSource, afterPlayingStreamState, this);
            fAreCurrentlyPlaying = True;
        }
    }
}

void StreamState::pause() {
    if (fRTPSink != NULL) fRTPSink->stopPlaying();
    if (fUDPSink != NULL) fUDPSink->stopPlaying();
    fAreCurrentlyPlaying = False;
}

void StreamState::endPlaying(Destinations *dests, unsigned clientSessionId) {
#if 0
    // The following code is temporarily disabled, because it erroneously sends RTCP "BYE"s to all clients if multiple
    // clients are streaming from the same data source (i.e., if "reuseFirstSource" is True), and we don't want that to happen
    // if we're being called as a result of a single one of these clients having sent a "TEARDOWN" (rather than the whole stream
    // having been closed, for all clients).
    // This will be fixed for real later.
    if (fRTCPInstance != NULL) {
      // Hack: Explicitly send a RTCP "BYE" packet now, because the code below will prevent that from happening later,
      // when "fRTCPInstance" gets deleted:
      fRTCPInstance->sendBYE();
    }
#endif

    if (dests->isTCP) {
        if (fRTPSink != NULL) {
            fRTPSink->removeStreamSocket(dests->tcpSocketNum, dests->rtpChannelId);
        }
        if (fRTCPInstance != NULL) {
            fRTCPInstance->removeStreamSocket(dests->tcpSocketNum, dests->rtcpChannelId);
            fRTCPInstance->unsetSpecificRRHandler(dests->tcpSocketNum, dests->rtcpChannelId);
        }
    } else {
        // Tell the RTP and RTCP 'groupsocks' to stop using these destinations:
        if (fRTPgs != NULL) fRTPgs->removeDestination(clientSessionId);
        if (fRTCPgs != NULL && fRTCPgs != fRTPgs) fRTCPgs->removeDestination(clientSessionId);
        if (fRTCPInstance != NULL) {
            fRTCPInstance->unsetSpecificRRHandler(dests->addr.s_addr, dests->rtcpPort);
        }
    }
}

void StreamState::sendRTCPAppPacket(u_int8_t subtype, char const *name,
                                    u_int8_t *appDependentData, unsigned appDependentDataSize) {
    if (fRTCPInstance != NULL) {
        fRTCPInstance->sendAppPacket(subtype, name, appDependentData, appDependentDataSize);
    }
}

void StreamState::reclaim() {
    // Delete allocated media objects
    Medium::close(fRTCPInstance) /* will send a RTCP BYE */;
    fRTCPInstance = NULL;
    Medium::close(fRTPSink);
    fRTPSink = NULL;
    Medium::close(fUDPSink);
    fUDPSink = NULL;

    fMaster.closeStreamSource(fMediaSource);
    fMediaSource = NULL;
    if (fMaster.fLastStreamToken == this) fMaster.fLastStreamToken = NULL;

    delete fRTPgs;
    if (fRTCPgs != fRTPgs) delete fRTCPgs;
    fRTPgs = NULL;
    fRTCPgs = NULL;
}
